package com.tools.web.work.excel.poi;

import com.tools.common.container.AbsOwnList;
import com.tools.common.io.IOKit;
import com.tools.common.object.C;
import com.tools.common.object.Note;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.jetbrains.annotations.NotNull;

import java.io.InputStream;
import java.util.*;


/**
 * 导入 Excel 文件的构建对象
 * */
@Note("导入 Excel 文件的构建对象")
public final class ExcelImporter implements Iterable<SheetImporter>, AutoCloseable {

    @Note("已读取的目标 Excel 文件")
    private Workbook workbook;

    @Note("当前使用中的 Sheet 的索引")
    private int sheetIndex = 0;

    @Note("Sheet 页结构列表。每个 Sheet 页对应其中的一个")
    private List<SheetImporter> sheetImporterList;

    @Note("读取的 Excel 文件的类型")
    private ExcelTypeEnum excelType;

    @Note("开始读取的行号（从 0 开始）")
    private int startRow;

    @Note("结束读取的行号（从 0 开始）。")
    private int endRow;

    @Note("是否记录错误数据并导出为 Excel")
    private boolean enableRecordError;

    @Note("读取出错时记录的错误信息列表。用于响应导出")
    private List<ErrorMsg> errorMsgList;

    /* ************************************************************************************************************
     *
     *      构造器
     *
     * ************************************************************************************************************
     * */

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, ExcelHead excelHead) {
        this(excelType, input, null, excelHead);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, ExcelHead excelHead) {
        this.private_initWorkbook(excelType, input);
        this.private_initSheetImporterList(excelHead, rowReadHandler);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, ExcelHead... excelHeads) {
        this(excelType, input, null, excelHeads);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, ExcelHead... excelHeads) {
        this.private_initWorkbook(excelType, input);
        this.private_initSheetImporterList(excelHeads, rowReadHandler);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, List<ExcelHead> excelHeads) {
        this(excelType, input, null, excelHeads);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, List<ExcelHead> excelHeads) {
        this.private_initWorkbook(excelType, input);
        this.private_initSheetImporterList(excelHeads, rowReadHandler);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, AbsOwnList<ExcelHead> excelHeads) {
        this(excelType, input, null, excelHeads);
    }

    public ExcelImporter(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, AbsOwnList<ExcelHead> excelHeads) {
        this.private_initWorkbook(excelType, input);
        this.private_initSheetImporterList(excelHeads, rowReadHandler);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, ExcelHead excelHead) {
        return new ExcelImporter(excelType, input, excelHead);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, ExcelHead excelHead) {
        return new ExcelImporter(excelType, input, rowReadHandler, excelHead);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, ExcelHead... excelHeads) {
        return new ExcelImporter(excelType, input, excelHeads);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, ExcelHead... excelHeads) {
        return new ExcelImporter(excelType, input, rowReadHandler, excelHeads);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, List<ExcelHead> excelHeads) {
        return new ExcelImporter(excelType, input, excelHeads);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, List<ExcelHead> excelHeads) {
        return new ExcelImporter(excelType, input, rowReadHandler, excelHeads);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, AbsOwnList<ExcelHead> excelHeads) {
        return new ExcelImporter(excelType, input, excelHeads);
    }

    public static ExcelImporter of(ExcelTypeEnum excelType, InputStream input, RowReadHandler<?> rowReadHandler, AbsOwnList<ExcelHead> excelHeads) {
        return new ExcelImporter(excelType, input, rowReadHandler, excelHeads);
    }

    /* ************************************************************************************************************
     *
     *      Getter / Setter
     *
     * ************************************************************************************************************
     * */

    public int getSheetIndex() {
        return sheetIndex;
    }

    public ExcelTypeEnum getExcelType() {
        return excelType;
    }

    public int getStartRow() {
        return startRow;
    }

    public void setStartRow(int startRow) {
        if((startRow < 0)) {
            this.resetStartRow();
            return;
        }
        this.startRow = startRow;
    }

    public ExcelImporter startRow(int startRow) {
        this.setStartRow(startRow);
        return this;
    }

    public int getEndRow() {
        return endRow;
    }

    public void setEndRow(int endRow) {
        int lastRowNum = this.workbook.getSheetAt(this.sheetIndex).getLastRowNum();
        if(endRow < 0 || endRow > lastRowNum) {
            this.endRow = lastRowNum;
            return;
        }
        this.endRow = endRow;
    }

    public ExcelImporter endRow(int endRow) {
        this.setEndRow(endRow);
        return this;
    }

    public int readCount() {
        return (endRow - startRow) + 1;
    }

    public ExcelImporter resetStartRow() {
        SheetImporter sheetImporter = this.sheetImporterList.get(this.sheetIndex);
        this.startRow = sheetImporter.getExcelHead().getMaxHeadRowNum();
        return this;
    }

    public ExcelImporter resetEndRow() {
        int lastRowNum = this.workbook.getSheetAt(this.sheetIndex).getLastRowNum();
        this.endRow = lastRowNum;
        return this;
    }

    public ExcelImporter resetReadRange() {
        return this.resetStartRow().resetEndRow();
    }

    public boolean isEnableRecordError() {
        return enableRecordError;
    }

    public void setEnableRecordError(boolean enableRecordError) {
        this.enableRecordError = enableRecordError;
    }

    public ExcelImporter enableRecordError(boolean enableRecordError) {
        this.setEnableRecordError(enableRecordError);
        return this;
    }

    public List<ErrorMsg> getErrorMsgList() {
        return errorMsgList;
    }

    /* ************************************************************************************************************
     *
     *      工具方法
     *
     * ************************************************************************************************************
     * */

    public ExcelImporter useSheet(int sheetIndex) {
        int sheetCount = this.sheetImporterList.size();
        if(sheetIndex < 0 || sheetIndex >= sheetCount) {
            throw new IllegalArgumentException("Excel 的总 sheet 数为: " + sheetCount + ", 当前索引为: " + sheetIndex);
        }
        this.sheetIndex = sheetIndex;
        return this.resetReadRange();
    }

    public boolean hasNext() {
        return (sheetIndex + 1) < sheetImporterList.size();
    }

    public boolean hasPrevious() {
        return (sheetIndex - 1) >= 0;
    }

    public ExcelImporter nextIndex() {
        int index = this.sheetIndex + 1;
        if(index < this.sheetImporterList.size()) {
            this.sheetIndex = index;
            return this.resetReadRange();
        }
        return this;
    }

    public ExcelImporter previousIndex() {
        int index = this.sheetIndex - 1;
        if(index >= 0) {
            this.sheetIndex = index;
            return this.resetReadRange();
        }
        return this;
    }

    public int sheetCount() {
        return sheetImporterList.size();
    }

    public Sheet currentSheet() {
        return this.workbook.getSheetAt(this.sheetIndex);
    }

    public Sheet sheet(int index) {
        int sheetCount = this.sheetImporterList.size();
        if(index < 0 || index >= sheetCount) {
            throw new IllegalArgumentException("Excel 的总 sheet 数为: " + sheetCount + ", 当前索引为: " + index);
        }
        return this.workbook.getSheetAt(index);
    }

    public Sheet nextSheet() {
        if(this.hasNext()) {
            return this.nextIndex().currentSheet();
        }
        return null;
    }

    public Sheet previousSheet() {
        if(this.hasPrevious()) {
            return this.previousIndex().currentSheet();
        }
        return null;
    }


    public SheetImporter currentSheetImporter() {
        return this.sheetImporterList.get(this.sheetIndex);
    }

    public SheetImporter sheetImporter(int index) {
        int sheetCount = this.sheetImporterList.size();
        if(index < 0 || index >= sheetCount) {
            throw new IllegalArgumentException("Excel 的总 sheet 数为: " + sheetCount + ", 当前索引为: " + index);
        }
        return this.sheetImporterList.get(index);
    }


    public SheetImporter nextSheetImporter() {
        if(this.hasNext()) {
            return this.nextIndex().currentSheetImporter();
        }
        return null;
    }

    public SheetImporter previousSheetImporter() {
        if(this.hasPrevious()) {
            return this.previousIndex().currentSheetImporter();
        }
        return null;
    }

    @Override
    public void close() {
        IOKit.close(this.workbook);
    }

    @NotNull
    @Override
    public Iterator<SheetImporter> iterator() {

        return new Iterator<SheetImporter>() {
            @Override
            public boolean hasNext() {
                return ExcelImporter.this.hasNext();
            }

            @Override
            public SheetImporter next() {
                return ExcelImporter.this.nextSheetImporter();
            }
        };
    }

    @Override
    public Spliterator<SheetImporter> spliterator() {
        return Spliterators.spliterator(
                this.iterator(),
                this.workbook.getNumberOfSheets(),
                Spliterator.ORDERED | Spliterator.SIZED | Spliterator.NONNULL
        );
    }

    public Iterator<Sheet> sheetIterator() {
        return new Iterator<Sheet>() {
            @Override
            public boolean hasNext() {
                return ExcelImporter.this.hasNext();
            }

            @Override
            public Sheet next() {
                return ExcelImporter.this.nextSheet();
            }
        };
    }

    public Spliterator<Sheet> sheetSpliterator() {
        return Spliterators.spliterator(
                this.sheetIterator(),
                this.workbook.getNumberOfSheets(),
                0
        );
    }



    public String getCellStringValue(Row row, int columnIndex, String errorMessage) {
        Cell cell = row.getCell(columnIndex);
        if(cell == null) {
            this.addError(row, columnIndex, errorMessage);
            return null;
        }
        return cell.toString();
    }

    public Integer getCellIntValue(Row row, int columnIndex, String errorMessage) {
        Cell cell = row.getCell(columnIndex);
        try {
            if(cell != null) {
                return Integer.parseInt(cell.toString());
            }
        } catch (Exception ignored) {}
        this.addError(row, columnIndex, errorMessage);
        return null;
    }


    public Long getCellLongValue(Row row, int columnIndex, String errorMessage) {
        Cell cell = row.getCell(columnIndex);
        try {
            if(cell != null) {
                return Long.parseLong(cell.toString());
            }
        } catch (Exception ignored) {}
        this.addError(row, columnIndex, errorMessage);
        return null;
    }


    public Double getCellDoubleValue(Row row, int columnIndex, String errorMessage) {
        Cell cell = row.getCell(columnIndex);
        try {
            if(cell != null) {
                return Double.parseDouble(cell.toString());
            }
        } catch (Exception ignored) {}
        this.addError(row, columnIndex, errorMessage);
        return null;
    }

    public Boolean getCellBooleanValue(Row row, int columnIndex, String errorMessage) {
        Cell cell = row.getCell(columnIndex);
        try {
            if(cell != null) {
                return Boolean.parseBoolean(cell.toString());
            }
        } catch (Exception ignored) {}
        this.addError(row, columnIndex, errorMessage);
        return null;
    }


    public <T> List<T> read(C<T> dataType) {
        Sheet sheet = this.workbook.getSheetAt(this.sheetIndex);
        return this.private_read(sheet, this.sheetIndex, this.startRow, this.endRow, dataType);
    }

    public <T> List<T> readAllSheet(C<T> dataType) {
        int numberOfSheets = this.workbook.getNumberOfSheets();
        List<T> result = new ArrayList<>();
        for (int i = 0; i < numberOfSheets; i++) {
            Sheet sheet = this.workbook.getSheetAt(i);
            int startRow = this.sheetImporterList.get(i).getMaxHeadRowNum();
            int endRow = sheet.getLastRowNum();
            List<T> data = this.private_read(sheet, i, startRow, endRow, dataType);
            result.addAll(data);
        }
        return result;
    }

    public Map<Integer, ExcelImg> readAllImageOfSheet() {
        Sheet sheet = this.workbook.getSheetAt(this.sheetIndex);
        return ExcelKit.readImgAsColumnIndexMap(sheet);
    }

    /* ************************************************************************************************************
     *
     *      错误信息列表的封装方法
     *
     * ************************************************************************************************************
     * */

    public ExcelImporter addError(Row row, int columnIndex, String errorMessage) {
        return this.addError(row.getRowNum(), columnIndex, errorMessage);
    }

    public ExcelImporter addError(int rowIndex, int columnIndex, String errorMessage) {
        if(!this.enableRecordError) return this;
        this.private_lazyLoadErrorMsgList(1);
        ErrorMsg errorMsg = new ErrorMsg()
                .location(this.sheetIndex, rowIndex, columnIndex)
                .message(errorMessage);
        this.errorMsgList.add(errorMsg);
        return this;
    }

    public ExcelImporter addError(String location, String errorMessage) {
        if(!this.enableRecordError) return this;
        this.private_lazyLoadErrorMsgList(1);
        ErrorMsg errorMsg = new ErrorMsg()
                .location(location)
                .message(errorMessage);
        this.errorMsgList.add(errorMsg);
        return this;
    }

    public ExcelImporter removeError(int index) {
        if(!this.enableRecordError || this.errorMsgList == null || this.errorMsgList.isEmpty()) {
            return this;
        }
        if(index >= 0 && index < this.errorMsgList.size()) {
            this.errorMsgList.remove(index);
        }
        return this;
    }


    public ErrorMsg getError(int index) {
        if(!this.enableRecordError || this.errorMsgList == null || this.errorMsgList.isEmpty()) {
            return null;
        }
        if(index >= 0 && index < this.errorMsgList.size()) {
            return this.errorMsgList.get(index);
        }
        return null;
    }

    public boolean hasError() {
        return this.enableRecordError && (this.errorMsgList != null && !this.errorMsgList.isEmpty());
    }


    /* ************************************************************************************************************
     *
     *      私有逻辑
     *
     * ************************************************************************************************************
     * */

    @Note("初始化 Workbook 对象")
    private void private_initWorkbook(ExcelTypeEnum excelType, InputStream excelFileInput) {
        if(excelType == null) throw new NullPointerException("Excel 类型枚举实例不能为 null");
        if(excelFileInput == null) throw new NullPointerException("Excel 文件的字节输入流为 null");
        this.workbook = ExcelKit.newWorkbook(excelType, excelFileInput);
        this.excelType = excelType;
    }

    @Note("初始化 Sheet 页的结构 List 集合")
    private void private_initSheetImporterList(ExcelHead excelHead, RowReadHandler<?> rowReadHandler) {
        int numberOfSheets = this.workbook.getNumberOfSheets();
        this.sheetImporterList = new ArrayList<>(numberOfSheets);
        for(int i = 0; i < numberOfSheets; i++) {
            this.sheetImporterList.add(new SheetImporter(excelHead, rowReadHandler));
        }
    }

    @Note("初始化 Sheet 页的结构 List 集合")
    private void private_initSheetImporterList(ExcelHead[] excelHeads, RowReadHandler<?> rowReadHandler) {
        if(excelHeads == null || excelHeads.length == 0) {
            throw new IllegalArgumentException("至少需要存在一个以上的 Excel 的表头配置对象不能为");
        }
        int numberOfSheets = this.workbook.getNumberOfSheets();
        if(numberOfSheets != excelHeads.length) {
            throw new IllegalArgumentException("Excel 文件的总 sheet 数与表头配置类的数量不符");
        }
        this.sheetImporterList = new ArrayList<>(numberOfSheets);
        for(int i = 0; i < numberOfSheets; i++) {
            ExcelHead excelHead = excelHeads[i];
            this.sheetImporterList.add(new SheetImporter(excelHead, rowReadHandler));
        }
    }


    @Note("初始化 Sheet 页的结构 List 集合")
    private void private_initSheetImporterList(List<ExcelHead> excelHeads, RowReadHandler<?> rowReadHandler) {
        if(excelHeads == null || excelHeads.isEmpty()) {
            throw new IllegalArgumentException("至少需要存在一个以上的 Excel 的表头配置对象不能为");
        }
        int numberOfSheets = this.workbook.getNumberOfSheets();
        if(numberOfSheets != excelHeads.size()) {
            throw new IllegalArgumentException("Excel 文件的总 sheet 数与表头配置类的数量不符");
        }
        this.sheetImporterList = new ArrayList<>(numberOfSheets);
        for(int i = 0; i < numberOfSheets; i++) {
            ExcelHead excelHead = excelHeads.get(i);
            this.sheetImporterList.add(new SheetImporter(excelHead, rowReadHandler));
        }
    }


    @Note("初始化 Sheet 页的结构 List 集合")
    private void private_initSheetImporterList(AbsOwnList<ExcelHead> excelHeads, RowReadHandler<?> rowReadHandler) {
        if(excelHeads != null && !excelHeads.isEmpty()) {
            int numberOfSheets = this.workbook.getNumberOfSheets();
            if(numberOfSheets == excelHeads.size()) {
                this.sheetImporterList = new ArrayList<>(numberOfSheets);
                for(int i = 0; i < numberOfSheets; i++) {
                    ExcelHead excelHead = excelHeads.get(i);
                    this.sheetImporterList.add(new SheetImporter(excelHead, rowReadHandler));
                }
                return;
            }
            throw new IllegalArgumentException("Excel 文件的总 sheet 数与表头配置类的数量不符");
        }
        throw new IllegalArgumentException("至少需要存在一个以上的 Excel 的表头配置对象不能为");
    }


    @Note("懒加载错误信息列表")
    private void private_lazyLoadErrorMsgList(int size) {
        if(this.errorMsgList != null) return;
        synchronized (this) {
            if(this.errorMsgList != null) return;
            this.errorMsgList = new ArrayList<>(size);
        }
    }

    @Note("读取操作")
    private <T> List<T> private_read(Sheet sheet, int currentUseSheetIndex, int start, int end,
                                     C<T> dataType) {
        SheetImporter sheetImporter = this.sheetImporterList.get(currentUseSheetIndex);
        return ExcelKit.readToList(this, sheetImporter, sheet, start, end, dataType);
    }

}
